//=============================================================================
//
//		Copyright (C) 1995, 1996 by Paul S. McCarthy and Eric Sunshine.
//				Written by Paul S. McCarthy and Eric Sunshine.
//							All Rights Reserved.
//
//		This notice may not be removed from this source code.
//
//		This object is included in the MiscKit by permission from the authors
//		and its use is governed by the MiscKit license, found in the file
//		"License.rtf" in the MiscKit distribution.	Please refer to that file
//		for a list of all applicable permissions and restrictions.
//
//=============================================================================
//-----------------------------------------------------------------------------
// MiscTableScroll.M
//
//		ScrollView class that displays a 2-D table of cells.
//
//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
// $Id: MiscTableScroll.M,v 1.18 96/01/16 19:55:04 zarnuk Exp $
// $Log:		MiscTableScroll.M,v $
// Revision 1.18  96/01/16	19:55:04  zarnuk
// Turns off display in -free.
// Does not call -update after calling -setAutodisplay:
// More careful about suppressing auto-sort after a drag.
// 
// Revision 1.17  96/01/13	23:33:54  zarnuk
// Pulled-out sorting stuff into (Sort) category.
// New version (1), since range of sortType values increased.
// Plugged some memory leaks.
// No longer tries to resort when "skip" slots are dragged.
// 
// Revision 1.16  95/12/19	23:16:38  sunshine
// Fixed MiscTableBorder.cc:1107: failed assertion 
// 
// Revision 1.15  95/12/17	15:11:35  zarnuk
// Added -border:sortSlot:, -sortCol:, -sortRow:.
// 
// Revision 1.14  95/10/20	00:12:40  sunshine
// Was including MiscTableScroll.h and MiscTableCell.h with "" instead of <>.
//-----------------------------------------------------------------------------
#import <misckit/MiscTableScroll.h>
#import <misckit/MiscTableCell.h>
#import "MiscTableBorder.h"
#import "MiscTableView.h"
#import "MiscColView.h"
#import "MiscRowView.h"
#import "MiscNullView.h"

#import <new.h>

extern "Objective-C" {
#import <appkit/Application.h>
#import <appkit/Cell.h>
#import <appkit/ClipView.h>
#import <appkit/Font.h>
#import <appkit/FontManager.h>
}

extern "C" {
#import <assert.h>
#import <math.h>
#import <strings.h>
}

// There are no structural differences between versions 0 and 1.  However,
// the range of the MiscSortType values increased from 0..8 to 0..12
#define MISC_TS_VERSION_0		0
#define MISC_TS_VERSION_1		1
#define MISC_TS_VERSION			MISC_TS_VERSION_1

@implementation MiscTableScroll

//-----------------------------------------------------------------------------
// + initialize
//-----------------------------------------------------------------------------
+ initialize
	{
	if (self == [MiscTableScroll class])
		{
		[self setVersion: MISC_TS_VERSION];
		}
	return self;
	}


//-----------------------------------------------------------------------------
// Tag Stuff
//-----------------------------------------------------------------------------
- (int) tag				{ return tag; }
- setTag:(int) x		{ tag = x;	return self; }


//-----------------------------------------------------------------------------
// -nextText
//-----------------------------------------------------------------------------
- nextText				{ return nextText; }
- previousText			{ return previousText; }
- setNextText:obj
	{
	nextText = obj;
	if (obj && [obj respondsTo: @selector(setPreviousText:)])
		[obj setPreviousText: self];
	return self;
	}
- setPreviousText:obj					{ previousText = obj; return self; }
- (BOOL) isSelectable					{ return YES; }
- (BOOL) isEnabled						{ return enabled; }
- setEnabled:(BOOL)flag					{ enabled = flag; return self; }

- selectText:sender
	{
	if ([window firstResponder] != (id)tableView)
		[window makeFirstResponder:tableView];
	return self;
	}


//-----------------------------------------------------------------------------
// Multicast Messages
//-----------------------------------------------------------------------------
- sendAction:(SEL)aSel to:obj forAllCells:(BOOL)flag
		{
		int const rlim = num_rows;
		int const clim = num_cols;
		for (int r = 0;	 r < rlim;	r++)
			for (int c = 0;	 c < clim;	c++)
				if (flag || [self cellIsSelected:r:c])
					if (![obj perform:aSel with:[self cellAt:r:c]])
						return self;
		return self;
		}

- (int) makeCellsPerform:(SEL)aSel with:p1 with:p2 selectedOnly:(BOOL)flag
		{
		int count = 0;
		int const rlim = num_rows;
		int const clim = num_cols;
		for (int r = 0;	 r < rlim;	r++)
			for (int c = 0;	 c < clim;	c++)
				if (!flag || [self cellIsSelected:r:c])
					{
					id cell = [self cellAt:r:c];
					if ([cell respondsTo:aSel])
						if ([cell perform:aSel with:p1 with:p2])
							count++;
						else
							return count;
					}
		return count;
		}

- (int) makeCellsPerform:(SEL)aSel with:p1 selectedOnly:(BOOL)flag
	{ return [self makeCellsPerform:aSel with:p1 with:0 selectedOnly:flag]; }
- (int) makeCellsPerform:(SEL)aSel selectedOnly:(BOOL)flag
	{ return [self makeCellsPerform:aSel with:0 with:0 selectedOnly:flag]; }

- (int) makeCellsPerform:(SEL)aSel
	{ return [self makeCellsPerform:aSel selectedOnly:NO]; }
- (int) makeCellsPerform:(SEL)aSel with:p1
	{ return [self makeCellsPerform:aSel with:p1 selectedOnly:NO]; }
- (int) makeCellsPerform:(SEL)aSel with:p1 with:p2
	{ return [self makeCellsPerform:aSel with:p1 with:p2 selectedOnly:NO];}


//-----------------------------------------------------------------------------
// FINDING CELLS
//-----------------------------------------------------------------------------

- (int) border:(MiscBorderType)b findSlotWithTag:(int)x
		{
		int const lim = (int) [self numSlots:b];
		for (int i = 0;	 i < lim;  i++)
			if ([self border:b slotTag:i] == x)
				return i;
		return -1;
		}
- (int) findColWithTag:(int)x
		{ return [self border:MISC_COL_BORDER findSlotWithTag:x]; }
- (int) findRowWithTag:(int)x
		{ return [self border:MISC_ROW_BORDER findSlotWithTag:x]; }


- findCell:cell row:(int*)row col:(int*)col
	{
	int const NRows = [self numRows];
	int const NCols = [self numCols];
	for (int r = 0;	 r < NRows;	 r++)
		for (int c = 0;	 c < NCols;	 c++)
			if ([self cellAt:r:c] == cell)
				{
				*row = r;
				*col = c;
				return self;
				}
	*row = -1;
	*col = -1;
	return 0;
	}

- findCellWithTag:(int)x row:(int*)row col:(int*)col
	{
	int const NRows = [self numRows];
	int const NCols = [self numCols];
	for (int r = 0;	 r < NRows;	 r++)
		for (int c = 0;	 c < NCols;	 c++)
			{
			id const cell = [self cellAt:r:c];
			if (cell && [cell respondsTo:@selector(tag)] && [cell tag] == x)
				{
				*row = r;
				*col = c;
				return cell;
				}
			}
	*row = -1;
	*col = -1;
	return 0;
	}

- findCellWithTag:(int)x
	{
	int r, c;
	return [self findCellWithTag:x row:&r col:&c];
	}




//-----------------------------------------------------------------------------
// Selection stuff
//-----------------------------------------------------------------------------
- (MiscSelectionMode) selectionMode { return mode; }
- (void) setSelectionMode:(MiscSelectionMode) x
	{
	if (x != mode)
		{
		mode = x;
		[colInfo.view setSelectionMode: mode];
		[rowInfo.view setSelectionMode: mode];
		[tableView setSelectionMode: mode];
		}
	}

- selectedCell { return 0; }	// FIXME: write this

- (BOOL) borderHasSelection: (MiscBorderType)b
		{ return info[b]->border->hasSelection(); }
- (BOOL) hasRowSelection { return [self borderHasSelection:MISC_ROW_BORDER]; }
- (BOOL) hasColSelection { return [self borderHasSelection:MISC_COL_BORDER]; }

- (BOOL) borderHasMultipleSelection: (MiscBorderType)b
		{ return info[b]->border->hasMultipleSelection(); }
- (BOOL) hasMultipleRowSelection
		{ return [self borderHasMultipleSelection: MISC_ROW_BORDER]; }
- (BOOL) hasMultipleColSelection
		{ return [self borderHasMultipleSelection: MISC_COL_BORDER]; }

- (unsigned int) borderNumSelectedSlots: (MiscBorderType)b
		{ return info[b]->border->numSelected(); }
- (unsigned int) numSelectedRows
		{ return [self borderNumSelectedSlots: MISC_ROW_BORDER]; }
- (unsigned int) numSelectedCols
		{ return [self borderNumSelectedSlots: MISC_COL_BORDER]; }

- (BOOL) border: (MiscBorderType)b slotIsSelected:(MiscCoord_P)slot
		{ return info[b]->border->slotIsSelected( slot ); }
- (BOOL) rowIsSelected:(MiscCoord_P)row
		{ return [self border:MISC_ROW_BORDER slotIsSelected:row]; }
- (BOOL) colIsSelected:(MiscCoord_P)col
		{ return [self border:MISC_COL_BORDER slotIsSelected:col]; }
- (BOOL) cellIsSelected:(MiscCoord_P)r :(MiscCoord_P)c
		{ return ([self rowIsSelected:r] || [self colIsSelected:c]); }

- (MiscCoord_P) borderSelectedSlot: (MiscBorderType)b
		{ return info[b]->border->selectedSlot(); }
- (MiscCoord_P) selectedRow
		{ return [self borderSelectedSlot:MISC_ROW_BORDER]; }
- (MiscCoord_P) selectedCol
		{ return [self borderSelectedSlot:MISC_COL_BORDER]; }

- (void) border: (MiscBorderType)b selectedTags: (MiscIntList*)tags
		{ info[b]->border->selectedTags( tags ); }
- (void) selectedRowTags: (MiscIntList*) tags
		{ [self border:MISC_ROW_BORDER selectedTags:tags]; }
- (void) selectedColTags: (MiscIntList*) tags
		{ [self border:MISC_COL_BORDER selectedTags:tags]; }

- (void) border: (MiscBorderType)b selectedSlots: (MiscIntList*)slots
		{ info[b]->border->selectedSlots( slots ); }
- (void) selectedRows: (MiscIntList*) rows
		{ [self border:MISC_ROW_BORDER selectedSlots:rows]; }
- (void) selectedCols: (MiscIntList*) cols
		{ [self border:MISC_COL_BORDER selectedSlots:cols]; }

- (void) selectionChanged
		{
		if ([self isAutodisplay] && [self canDraw])
			[self reflectSelection];
		else
			[self setNeedsDisplay:YES];
		}

- (void) border: (MiscBorderType)b selectSlot:(MiscCoord_P)slot
		{
		BOOL changed = NO;
		MiscTableBorder* const bp = info[b]->border;
		unsigned int const n = bp->numSelected();
		if (0 <= slot && slot < bp->count())
			{
			if (n != 1 || bp->selectedSlot() != slot)
				{
				bp->selectSlot( slot );
				changed = YES;
				}
			}
		else
			{
			if (n > 0)
				{
				bp->selectNone();
				changed = YES;
				}
			}
		if (changed)
			[self selectionChanged];
		}
- (void) selectRow:(MiscCoord_P)row
		{ [self border:MISC_ROW_BORDER selectSlot:row]; }
- (void) selectCol:(MiscCoord_P)col
		{ [self border:MISC_COL_BORDER selectSlot:col]; }

- (void) border: (MiscBorderType)b selectTags: (MiscIntList*)tags
		{
		info[b]->border->selectTags( tags );
		[self selectionChanged];
		}
- (void) selectRowTags: (MiscIntList*) tags
		{ [self border:MISC_ROW_BORDER selectTags:tags]; }
- (void) selectColTags: (MiscIntList*) tags
		{ [self border:MISC_COL_BORDER selectTags:tags]; }

- (void) border: (MiscBorderType)b selectSlots: (MiscIntList*)slots
		{
		info[b]->border->selectSlots( slots );
		[self selectionChanged];
		}
- (void) selectRows: (MiscIntList*) rows
		{ [self border:MISC_ROW_BORDER selectSlots:rows]; }
- (void) selectCols: (MiscIntList*) cols;
		{ [self border:MISC_COL_BORDER selectSlots:cols]; }

- (void) borderSelectAll: (MiscBorderType)b
		{
		if (mode == MISC_LIST_MODE || mode == MISC_HIGHLIGHT_MODE)
			{
			info[b]->border->selectAll();
			[self selectionChanged];
			}
		}
- (void) selectAllRows { [self borderSelectAll: MISC_ROW_BORDER]; }
- (void) selectAllCols { [self borderSelectAll: MISC_COL_BORDER]; }
- selectAll:sender
		{
		[self selectAllRows];
		[self sendActionIfEnabled];
		return self;
		}

- (void) borderClearSelection: (MiscBorderType)b
		{
		info[b]->border->selectNone();
		[self selectionChanged];
		}
- (void) clearRowSelection { [self borderClearSelection: MISC_ROW_BORDER]; }
- (void) clearColSelection { [self borderClearSelection: MISC_COL_BORDER]; }
- (void) clearSelection { [self clearRowSelection]; [self clearColSelection]; }


//-----------------------------------------------------------------------------
// Keyboard cursor stuff
//-----------------------------------------------------------------------------
- (void) reflectCursor { [tableView reflectCursor]; }
- (MiscCoord_P) borderCursor: (MiscBorderType)b
		{ return info[b]->border->getCursor_P(); }
- (MiscCoord_P) cursorRow { return [self borderCursor: MISC_ROW_BORDER]; }
- (MiscCoord_P) cursorCol { return [self borderCursor: MISC_COL_BORDER]; }

- (void) border: (MiscBorderType)b setCursor: (MiscCoord_P) slot
		{ info[b]->border->setCursor_P( slot ); [self reflectCursor]; }
- (void) setCursorRow: (MiscCoord_P) row
		{ [self border: MISC_ROW_BORDER setCursor: row]; }
- (void) setCursorCol: (MiscCoord_P) col
		{ [self border: MISC_COL_BORDER setCursor: col]; }

- (void) borderClearCursor: (MiscBorderType)b
		{ info[b]->border->clearCursor(); [self reflectCursor]; }
- (void) clearCursorRow { [self borderClearCursor: MISC_ROW_BORDER]; }
- (void) clearCursorCol { [self borderClearCursor: MISC_COL_BORDER]; }
- (void) clearCursor { [self clearCursorRow]; [self clearCursorCol]; }

- (BOOL) borderHasValidCursor: (MiscBorderType)b
		{ return info[b]->border->hasValidCursor(); }
- (BOOL) hasValidCursorRow
		{ return [self borderHasValidCursor: MISC_ROW_BORDER]; }
- (BOOL) hasValidCursorCol
		{ return [self borderHasValidCursor: MISC_COL_BORDER]; }


//-----------------------------------------------------------------------------
// Overrides...	 Scrollers are always required.
//-----------------------------------------------------------------------------
- setHorizScrollerRequired: (BOOL) flag { return self; }
- setVertScrollerRequired: (BOOL) flag	{ return self; }


//-----------------------------------------------------------------------------
// - getDocClipFrame:
//-----------------------------------------------------------------------------
- (void) getDocClipFrame: (NXRect*) rect
	{
	[contentView getFrame: rect];
	}


//-----------------------------------------------------------------------------
// - freeBorder:
//-----------------------------------------------------------------------------
- (void) freeBorder:(MiscBorderInfo*)p
	{
	[p->view removeFromSuperview];
	[p->view free];
	[p->clip removeFromSuperview];
	[p->clip free];
	delete p->border;
	}


//-----------------------------------------------------------------------------
// - free
//-----------------------------------------------------------------------------
- free
	{
	[self setAutodisplay:NO];			// Don't do any drawing!
	[self emptyAndFreeCells];
	[self freeBorder:&colInfo];
	[self freeBorder:&rowInfo];
	[nullView removeFromSuperview];
	[nullView free];
	[tableView removeFromSuperview];
	[tableView free];
	return [super free];
	}


//-----------------------------------------------------------------------------
// get/set min/max total size limits.
//-----------------------------------------------------------------------------
- (NXCoord) totalSize:(MiscBorderType)b
		{ return (NXCoord) info[b]->border->totalSize(); }
- (NXCoord) totalWidth
		{ return [self totalSize:MISC_COL_BORDER]; }
- (NXCoord) totalHeight
		{ return [self totalSize:MISC_ROW_BORDER]; }

- (NXCoord) minTotalSize:(MiscBorderType)b
		{ return (NXCoord) info[b]->border->getMinTotalSize(); }
- (NXCoord) maxTotalSize:(MiscBorderType)b
		{ return (NXCoord) info[b]->border->getMaxTotalSize(); }
- (NXCoord) minTotalWidth
		{ return [self minTotalSize:MISC_COL_BORDER]; }
- (NXCoord) maxTotalWidth
		{ return [self maxTotalSize:MISC_COL_BORDER]; }
- (NXCoord) minTotalHeight
		{ return [self minTotalSize:MISC_ROW_BORDER]; }
- (NXCoord) maxTotalHeight
		{ return [self maxTotalSize:MISC_ROW_BORDER]; }

// FIXME: Need to decide whether or not to update the views here.
// FIXME: Should check current value before setting.
- (void) border:(MiscBorderType)b setMinTotalSize:(NXCoord)size
		{ info[b]->border->setMinTotalSize( (MiscPixels)floor(size) ); }
- (void) border:(MiscBorderType)b setMaxTotalSize:(NXCoord)size
		{ info[b]->border->setMaxTotalSize( (MiscPixels)floor(size) ); }
- (void) setMinTotalWidth:(NXCoord)size
		{ [self border:MISC_COL_BORDER setMinTotalSize:size]; }
- (void) setMaxTotalWidth:(NXCoord)size
		{ [self border:MISC_COL_BORDER setMaxTotalSize:size]; }
- (void) setMinTotalHeight:(NXCoord)size
		{ [self border:MISC_ROW_BORDER setMinTotalSize:size]; }
- (void) setMaxTotalHeight:(NXCoord)size
		{ [self border:MISC_ROW_BORDER setMaxTotalSize:size]; }


//-----------------------------------------------------------------------------
// - constrainSize
//-----------------------------------------------------------------------------
- (void) constrainSize
	{
	NXRect r;
	[self getDocClipFrame: &r];
	if ([self minTotalSizeIsConstrained:MISC_COL_BORDER])
		[self border:MISC_COL_BORDER setMinTotalSize: r.size.width];
	if ([self maxTotalSizeIsConstrained:MISC_COL_BORDER])
		[self border:MISC_COL_BORDER setMaxTotalSize: r.size.width];
	if ([self minTotalSizeIsConstrained:MISC_ROW_BORDER])
		[self border:MISC_ROW_BORDER setMinTotalSize: r.size.height];
	if ([self maxTotalSizeIsConstrained:MISC_ROW_BORDER])
		[self border:MISC_ROW_BORDER setMaxTotalSize: r.size.height];
	[rowInfo.view adjustSize];
	[colInfo.view adjustSize];
	[tableView adjustSize];
	}


//-----------------------------------------------------------------------------
// - sizeToCells
//-----------------------------------------------------------------------------
- sizeToCells
	{
	[self constrainSize];
	return self;
	}


//-----------------------------------------------------------------------------
// get/set min/max constrained
//-----------------------------------------------------------------------------
- (BOOL) minTotalSizeIsConstrained:(MiscBorderType)b
		{ return info[b]->constrain_min; }
- (BOOL) maxTotalSizeIsConstrained:(MiscBorderType)b
		{ return info[b]->constrain_max; }
- (BOOL) minTotalWidthIsConstrained
		{ return [self minTotalSizeIsConstrained:MISC_COL_BORDER]; }
- (BOOL) maxTotalWidthIsConstrained
		{ return [self maxTotalSizeIsConstrained:MISC_COL_BORDER]; }
- (BOOL) minTotalHeightIsConstrained
		{ return [self minTotalSizeIsConstrained:MISC_ROW_BORDER]; }
- (BOOL) maxTotalHeightIsConstrained
		{ return [self maxTotalSizeIsConstrained:MISC_ROW_BORDER]; }

- (void) border:(MiscBorderType)b constrainMinTotalSize:(BOOL)flag;
		{
		MiscBorderInfo* ip = info[b];
		if (ip->constrain_min != flag)
			{
			ip->constrain_min = flag;
			if (!flag)
				[self border:MISC_COL_BORDER setMinTotalSize:0];
			[self constrainSize];
			}
		}

- (void) border:(MiscBorderType)b constrainMaxTotalSize:(BOOL)flag
		{
		MiscBorderInfo* ip = info[b];
		if (ip->constrain_max != flag)
			{
			ip->constrain_max = flag;
			if (!flag)
				[self border:MISC_COL_BORDER
						setMaxTotalSize: (NXCoord) MISC_MAX_PIXELS_SIZE ];
			[self constrainSize];
			}
		}

- (void) constrainMinTotalWidth:(BOOL)flag
		{ [self border:MISC_COL_BORDER constrainMinTotalSize:flag]; }
- (void) constrainMaxTotalWidth:(BOOL)flag
		{ [self border:MISC_COL_BORDER constrainMaxTotalSize:flag]; }
- (void) constrainMinTotalHeight:(BOOL)flag
		{ [self border:MISC_ROW_BORDER constrainMinTotalSize:flag]; }
- (void) constrainMaxTotalHeight:(BOOL)flag
		{ [self border:MISC_ROW_BORDER constrainMaxTotalSize:flag]; }


//-----------------------------------------------------------------------------
// - sizeTo::
//-----------------------------------------------------------------------------
- sizeTo:(NXCoord)width :(NXCoord)height
	{
	[super sizeTo:width:height];
	[self constrainSize];
	return self;
	}


//-----------------------------------------------------------------------------
// - forwardBGColor
//-----------------------------------------------------------------------------
- (void) forwardBGColor
	{
	[super setBackgroundColor: backgroundColor];
	[colInfo.clip setBackgroundColor: backgroundColor];
	[rowInfo.clip setBackgroundColor: backgroundColor];
	}


//-----------------------------------------------------------------------------
// - initBorder:type:
//-----------------------------------------------------------------------------
- (void) initBorder:(MiscBorderInfo*) p type:(MiscBorderType) type
	{
	NXZone* const z = [self zone];
	if (p->border == 0)
		p->border = new( NXZoneMalloc(z,sizeof(*(p->border))) )
						MiscTableBorder( type );
	p->border->setOwner( self );

	if (type == MISC_COL_BORDER)
		p->view = [[MiscColView allocFromZone:z] 
						initFrame:0 scroll:self info:p->border];
	else
		p->view = [[MiscRowView allocFromZone:z]
						initFrame:0 scroll:self info:p->border];

	p->clip = [[ClipView allocFromZone:z] initFrame:0];
	[p->clip setDocView:p->view];
	if (p->isOn)
		{
		[self addSubview: p->clip];
		[window invalidateCursorRectsForView: p->view];
		}
	}


//-----------------------------------------------------------------------------
// - doInit:
//
//		NOTE *1*: The tableView was initialized with an argument of 0 so it's
//				frame is invalid.  Calling constrainSize with an invalid frame
//				can result in crashers when it tries to access slots outside
//				of the appropriate area.  Calling -tile first ensures that the
//				frame is valid.
//-----------------------------------------------------------------------------
- (void) doInit:(int)ver
	{
	[window disableDisplay];
	[self setBorderType: NX_BEZEL];
	[super setHorizScrollerRequired: YES];
	[super setVertScrollerRequired: YES];

	info[ MISC_COL_BORDER ] = &colInfo;
	info[ MISC_ROW_BORDER ] = &rowInfo;

	[self initBorder:&colInfo type:MISC_COL_BORDER];
	[self initBorder:&rowInfo type:MISC_ROW_BORDER];

	NXZone* const z = [self zone];

	tableView = [[MiscTableView allocFromZone:z] initFrame:0
		scroll:self
		colInfo:colInfo.border
		rowInfo:rowInfo.border];

	nullView = [[MiscNullView allocFromZone:z] initFrame:0];

	if (colInfo.isOn && rowInfo.isOn)
		[self addSubview:nullView];

	id oldView = [self setDocView: tableView];
	[oldView free];
	[self tile];										// NOTE *1*
	[self constrainSize];
	[self forwardBGColor];

	[self registerServicesTypes];
	[window reenableDisplay];
	}


//-----------------------------------------------------------------------------
// - initFrame:
//-----------------------------------------------------------------------------
- initFrame: (NXRect const*) frameRect
	{
	[super initFrame: frameRect];
	font = [[self class] defaultFont];
	tag = 0;
	enabled = YES;
	colInfo.border = 0; colInfo.isOn = YES;
	colInfo.constrain_min = YES;
	colInfo.constrain_max = NO;
	rowInfo.border = 0; rowInfo.isOn = NO;
	rowInfo.constrain_min = YES;
	rowInfo.constrain_max = NO;
	textColor = [[self class] defaultTextColor];
	backgroundColor = [[self class] defaultBackgroundColor];
	highlightTextColor = [[self class] defaultHighlightTextColor];
	highlightBackgroundColor = [[self class] defaultHighlightBackgroundColor];

	[self doInit:MISC_TS_VERSION];

	if ([self respondsTo:@selector(initIB)]) [self perform:@selector(initIB)];
	return self;
	}


//-----------------------------------------------------------------------------
// - setFrame:
//-----------------------------------------------------------------------------
- setFrame: (NXRect const*) frameRect
	{
	[super setFrame:frameRect];
	[self constrainSize];
	return self;
	}


//-----------------------------------------------------------------------------
// - adjustSizes
//-----------------------------------------------------------------------------
- (void) adjustSizes
	{
	if (colInfo.isOn && colInfo.view != 0)
		[colInfo.view adjustSize];
	if (rowInfo.isOn && rowInfo.view != 0)
		[rowInfo.view adjustSize];
	[tableView adjustSize];
	}


//-----------------------------------------------------------------------------
// - tile
//-----------------------------------------------------------------------------
- tile
	{
	[window disableDisplay];
	[super tile];
	[self adjustSizes];

	if (colInfo.isOn && !rowInfo.isOn)
		{
		NXCoord colHeight = [colInfo.view frameHeight];
		NXRect colRect;
		NXRect docRect;

		[contentView getFrame: &docRect];

		NXDivideRect( &docRect, &colRect, colHeight, NX_YMIN );

		[colInfo.clip setFrame: &colRect];
		[contentView setFrame: &docRect];
		[window invalidateCursorRectsForView: colInfo.view];
		}
	else if (!colInfo.isOn && rowInfo.isOn)
		{
		NXCoord rowWidth  = [rowInfo.view frameHeight];
		NXRect rowRect;
		NXRect docRect;

		[contentView getFrame: &docRect];

		NXDivideRect( &docRect, &rowRect, rowWidth, NX_XMIN );

		[rowInfo.clip setFrame: &rowRect];
		[contentView setFrame: &docRect];
		[window invalidateCursorRectsForView: rowInfo.view];
		}
	else if (colInfo.isOn && rowInfo.isOn)
		{
		NXCoord colHeight = [colInfo.view frameHeight];
		NXCoord rowWidth  = [rowInfo.view frameHeight];
		NXRect nulRect;
		NXRect colRect;
		NXRect rowRect;
		NXRect docRect;

		[contentView getFrame: &docRect];

		NXDivideRect( &docRect, &colRect, colHeight, NX_YMIN );
		NXDivideRect( &colRect, &nulRect, rowWidth, NX_XMIN );
		NXDivideRect( &docRect, &rowRect, rowWidth, NX_XMIN );

		[nullView setFrame: &nulRect];
		[colInfo.clip setFrame: &colRect];
		[rowInfo.clip setFrame: &rowRect];
		[contentView setFrame: &docRect];
		[window invalidateCursorRectsForView: colInfo.view];
		[window invalidateCursorRectsForView: rowInfo.view];
		}

	[window reenableDisplay];
	return self;
	}


//-----------------------------------------------------------------------------
// - reflectScroll:
//-----------------------------------------------------------------------------
- reflectScroll: aView
	{
	if (aView == contentView) // only reflect position of contentView
		[super reflectScroll: aView];
	return self;
	}


//-----------------------------------------------------------------------------
// - scrollClip:to:
//-----------------------------------------------------------------------------
- scrollClip: aClipView to: (const NXPoint *) aPoint
	{
	if (aClipView == contentView)				// contentView only.
		{
		NXRect rect;
		[window disableFlushWindow];

		[aClipView rawScroll: aPoint];			// Scroll content

		if (colInfo.isOn)
			{
			[colInfo.clip getBounds: &rect];	// Maybe scroll col headings.
			if (rect.origin.x != aPoint->x)
				{
				rect.origin.x = aPoint->x;
				[colInfo.clip rawScroll: &rect.origin];
				[window invalidateCursorRectsForView: colInfo.view];
				}
			}

		if (rowInfo.isOn)
			{
			[rowInfo.clip getBounds: &rect];	// Maybe scroll row labels.
			if (rect.origin.y != aPoint->y)
				{
				rect.origin.y = aPoint->y;
				[rowInfo.clip rawScroll: &rect.origin];
				[window invalidateCursorRectsForView: rowInfo.view];
				}
			}

		[[window reenableFlushWindow] flushWindow];
		}
	else if (aClipView == (id)colInfo.clip)
		{
		NXRect rect;
		[contentView getBounds: &rect];
		rect.origin.x = aPoint->x;
		[self scrollClip:contentView to:&(rect.origin)];
		[self reflectScroll:contentView];
		}
	else if (aClipView == (id)rowInfo.clip)
		{
		NXRect rect;
		[contentView getBounds: &rect];
		rect.origin.y = aPoint->y;
		[self scrollClip:contentView to:&(rect.origin)];
		[self reflectScroll:contentView];
		}
	return self;
	}



//-----------------------------------------------------------------------------
// Delegate Stuff
//-----------------------------------------------------------------------------
- delegate						{ return delegate; }
- setDelegate:obj				{ delegate = obj; return self; }
- dataDelegate					{ return dataDelegate; }
- setDataDelegate:obj			{ dataDelegate = obj; return self; }

- (char const*) border:(MiscBorderType)b getDelegateSlotTitle:(int)slot
	{
	if (delegate != 0 &&
		[delegate respondsTo:@selector(tableScroll:border:slotTitle:)])
		return [delegate tableScroll:self border:b slotTitle:slot];

	if (dataDelegate != 0 &&
		[dataDelegate respondsTo:@selector(tableScroll:border:slotTitle:)])
		return [dataDelegate tableScroll:self border:b slotTitle:slot];

	return 0;
	}

- border:(MiscBorderType)b getDelegateSlotPrototype:(int)s
	{
	if (delegate != 0 &&
		[delegate respondsTo:@selector(tableScroll:border:slotPrototype:)])
		return [delegate tableScroll:self border:b slotPrototype:s];

	if (dataDelegate != 0 &&
		[dataDelegate respondsTo:@selector(tableScroll:border:slotPrototype:)])
		return [dataDelegate tableScroll:self border:b slotPrototype:s];

	return 0;
	}

- (void) border:(MiscBorderType)b slotDraggedFrom:(int)fromPos to:(int)toPos
	{
	MiscBorderType const ob = otherBorder(b);
	if ([self autoSortSlots:ob])
		{
		int slot = [self border:b slotAtPosition:toPos];
		if ([self border:b slotSortType:slot] != MISC_SORT_SKIP ||
			[self border:b slotSortFunc:slot] != 0)
			[self sortSlots:ob]; // Don't resort if it doesn't affect ordering.
		}
	if (delegate != 0 &&
		[delegate respondsTo:@selector(tableScroll:border:slotDraggedFrom:to:)])
		[delegate tableScroll:self border:b slotDraggedFrom:fromPos to:toPos];
	}

- (void) border:(MiscBorderType)b slotResized:(int)n
	{
	if (delegate != 0 &&
		[delegate respondsTo:@selector(tableScroll:border:slotResized:)])
		[delegate tableScroll:self border:b slotResized:n];
	}


//-----------------------------------------------------------------------------
// Target / Action
//-----------------------------------------------------------------------------
- target						{ return target; }
- setTarget:obj					{ target = obj; return self; }
- doubleTarget					{ return doubleTarget; }
- setDoubleTarget:obj			{ doubleTarget = obj; return self; }
- (SEL) action					{ return action; }
- setAction:(SEL)new_sel		{ action = new_sel; return self; }
- setDoubleAction:(SEL)new_sel	{ doubleAction = new_sel; return self; }
- (SEL) doubleAction			{ return doubleAction; }

- sendAction:(SEL)aSel to:obj
		{
		if (aSel == 0)
			aSel = action;
		if (obj == 0)
			obj = target;
		return ([NXApp sendAction:aSel to:obj from:self] ? self : 0);
		}

- (SEL) getCellAction:cell
		{
		if ([cell respondsTo:@selector(action)])
			return [cell action];
		return 0;
		}

- getCellTarget:cell
		{
		if ([cell respondsTo:@selector(target)])
			return [cell target];
		return 0;
		}

- sendAction
		{
		id cell = [self selectedCell];
		return [self sendAction:[self getCellAction:cell]
								to:[self getCellTarget:cell]];
		}

- sendDoubleAction
		{
		return [self sendAction:doubleAction to:doubleTarget];
		}

- sendActionIfEnabled
		{
		if ([self isEnabled])
			return [self sendAction];
		return 0;
		}

- sendDoubleActionIfEnabled
		{
		if ([self isEnabled])
			return [self sendDoubleAction];
		return 0;
		}


//-----------------------------------------------------------------------------
// FONT
//-----------------------------------------------------------------------------
+ (Font*) defaultFont
	{
	return [Font userFontOfSize:12.0 matrix:NX_FLIPPEDMATRIX];
	}

- font
	{
	return font;
	}


static double get_height( Font* font )
	{
	NXFontMetrics const* const p = [font metrics];
	double const size = [font pointSize];
	return ((p->fontBBox[3] - p->fontBBox[1]) * size);
	}


- setFont:newFont
	{
	if (newFont != 0 && newFont != (id)font && [newFont isKindOf:[Font class]])
		{
		[window disableDisplay];
		Font* oldFont = font;
		font = newFont;
		NXCoord old_size = [self uniformSizeRows];
		if (old_size != 0)
			{
			// FIXME: Handle this better.  Different cell-types will have
			// different amounts of "fixed-size" border stuff.
			NXCoord const BORDER_THICKNESS = 1;
			old_size -= BORDER_THICKNESS;
			NXCoord const new_size =
				floor(	0.5 + (double(old_size) *
						(get_height( newFont ) / get_height( oldFont ))));
			if (new_size != old_size)
				[self setUniformSizeRows:(new_size + BORDER_THICKNESS)];
			}
		// FIXME: Set font in all existing prototype cells.
		// WARNING: Currently, just asking the border for a prototype
		// in a given slot allocates and initializes an array of prototypes.
		if (![self isLazy])				// Eager beaver sets all cells now.
			{
			int const NRows = num_rows;
			int const NCols = num_cols;
			for (int r = 0;	 r < NRows;	 r++)
				{
				for (int c = 0;	 c < NCols;	 c++)
					{
					id cell = [self cellAt:r:c];
					if (cell != 0)
						{
						if ([cell respondsTo:@selector(setOwnerFont:)])
							[cell setOwnerFont:newFont];
						else if ([cell respondsTo:@selector(setFont:)])
							[cell setFont:newFont];
						}
					}
				}
			}
		if (delegate != 0 &&
			[delegate respondsTo:@selector(tableScroll:fontChangedFrom:to:)])
			[delegate tableScroll:self fontChangedFrom:oldFont to:newFont];
		if (dataDelegate != 0 &&
			[dataDelegate respondsTo:
						@selector(tableScroll:fontChangedFrom:to:)])
			[dataDelegate tableScroll:self fontChangedFrom:oldFont to:newFont];

		[window reenableDisplay];
		[self update];
		}
	return self;
	}


- changeFont:sender
	{
	FontManager* fontMgr = [FontManager new];
	Font* newFont = [fontMgr convertFont:[fontMgr selFont]];
	if (newFont != 0 && newFont != font)
		{
		[window disableDisplay];
		Font* oldFont = font;
		[self setFont:newFont];
		if (delegate != 0 &&
			[delegate respondsTo: @selector(tableScroll:changeFont:to:)])
			[delegate tableScroll:self changeFont:oldFont to:newFont];
		if (dataDelegate != 0 &&
			[dataDelegate respondsTo: @selector(tableScroll:changeFont:to:)])
			[dataDelegate tableScroll:self changeFont:oldFont to:newFont];
		[window reenableDisplay];
		[self update];
		}
	return self;
	}


//-----------------------------------------------------------------------------
// COLOR
//
// The following macros expand into the implementations for these functions.
// Their names are listed so they can be found when searched for.
// setBackgroundColor:, setTextColor:,
// setHighlightBackgroundColor:, setHighlightTextColor:
// setBackgroundGray:, setTextGray:,
// setHighlightBackgroundGray:, setHighlightTextGray:
// backgroundGray:, textGray:, highlightBackgroundGray:, highlightTextGray:
//-----------------------------------------------------------------------------
+ (NXColor) defaultBackgroundColor				{ return NX_COLORLTGRAY; }
+ (NXColor) defaultTextColor					{ return NX_COLORBLACK; }
+ (NXColor) defaultHighlightBackgroundColor		{ return NX_COLORWHITE; }
+ (NXColor) defaultHighlightTextColor			{ return NX_COLORBLACK; }

- (NXColor) backgroundColor				{ return backgroundColor; }
- (NXColor) textColor					{ return textColor; }
- (NXColor) highlightBackgroundColor	{ return highlightBackgroundColor; }
- (NXColor) highlightTextColor			{ return highlightTextColor; }

- setColor:(NXColor) value		{ return [self setBackgroundColor:value]; }
- (NXColor) color				{ return [self backgroundColor]; }

- setColor:(NXColor)x			// New color value.
		var:(NXColor*)v			// Instance variable for the color.
		sel1:(SEL)sel1			// "setOwner...Color:" message for cell.
		sel2:(SEL)sel2			// "set...Color:" message for cell.
		notifySel:(SEL)notifySel		// "..ColorChangedTo:" message
	{
	if (!NXEqualColor( x, *v ))
		{
		BOOL const was_auto = [self isAutodisplay];
		[self setAutodisplay:NO];

		*v = x;
		if (v == &backgroundColor) [self forwardBGColor];

		if (![self isLazy])
			{
			int const NRows = num_rows;
			int const NCols = num_cols;
			for (int r = 0; r < NRows;	r++)
				for (int c = 0;	 c < NCols;	 c++)
					{
					id cell = [self cellAt:r:c];
					if (cell != 0)
						{
						if ([cell respondsTo:sel1])
							(*[cell methodFor:sel1])( cell, sel1, x );
						else if ([cell respondsTo:sel2])
							(*[cell methodFor:sel2])( cell, sel2, x );
						}
					}
			}

		if (delegate != 0 && [delegate respondsTo:notifySel])
			(*[delegate methodFor:notifySel])( delegate, notifySel, self, x );

		if (dataDelegate != 0 && [dataDelegate respondsTo:notifySel])
			(*[dataDelegate methodFor:notifySel])
						( dataDelegate, notifySel, self, x );

		[self setNeedsDisplay:YES];
		[self setAutodisplay:was_auto];			// Will display if needed.
		}

	return self;
	}


#define MISC_SET_COLOR_FUNC(LNAME,CNAME)\
- set##CNAME##Color: (NXColor) value\
	{ return [self setColor:value\
				var:& LNAME##Color\
				sel1:@selector(setOwner##CNAME##Color:)\
				sel2:@selector(set##CNAME##Color:)\
				notifySel:@selector(tableScroll:LNAME##ColorChangedTo:)]; }

MISC_SET_COLOR_FUNC( background, Background )
MISC_SET_COLOR_FUNC( text, Text )
MISC_SET_COLOR_FUNC( highlightBackground, HighlightBackground )
MISC_SET_COLOR_FUNC( highlightText, HighlightText )
#undef MISC_SET_COLOR_FUNC


static inline float color_to_gray( NXColor x )
	{
	float gray;
	NXConvertColorToGray( x, &gray );
	return gray;
	}


#define MISC_GET_GRAY_FUNC( LNAME )\
- (float) LNAME##Gray { return color_to_gray( [self LNAME##Color] ); }

MISC_GET_GRAY_FUNC( background )
MISC_GET_GRAY_FUNC( text )
MISC_GET_GRAY_FUNC( highlightBackground )
MISC_GET_GRAY_FUNC( highlightText )
#undef MISC_GET_GRAY_FUNC

#define MISC_SET_GRAY_FUNC( CNAME )\
- set##CNAME##Gray: (float) x\
	{ return [self set##CNAME##Color: NXConvertGrayToColor(x)]; }

MISC_SET_GRAY_FUNC( Background )
MISC_SET_GRAY_FUNC( Text )
MISC_SET_GRAY_FUNC( HighlightBackground )
MISC_SET_GRAY_FUNC( HighlightText )
#undef MISC_SET_GRAY_FUNC


//-----------------------------------------------------------------------------
// SAVE / RESTORE
//-----------------------------------------------------------------------------

- border:(MiscBorderType)b slotOrder:(MiscIntList*)list
	{
	if (list != 0)
		{
		MiscTableBorder const* const bp = info[b]->border;
		MiscCoord_P const* const vmap = bp->getVMap();
		int const lim = bp->count();
		[list empty];
		for (int i = 0;	 i < lim;  i++)
			[list addInt: (vmap ? vmap[i] : i) ];
		return self;
		}
	return 0;
	}
- colOrder:(MiscIntList*)list
	{ return [self border:MISC_COL_BORDER slotOrder:list]; }
- rowOrder:(MiscIntList*)list
	{ return [self border:MISC_ROW_BORDER slotOrder:list]; }

- border:(MiscBorderType)b setSlotOrder:(MiscIntList*)list
	{
	id retval = 0;
	if (list != 0)
		{
		MiscTableBorder* const bp = info[b]->border;
		int const lim = bp->count();
		if ([list count] == lim)
			if (bp->setVMap( [list rawData] ))
				{
				MiscBorderType const ob = otherBorder(b);
				if ([self autoSortSlots:ob])
					[self sortSlots:ob];
				retval = self;
				}
		}
	return retval;
	}
- setColOrder:(MiscIntList*)list
	{ return [self border:MISC_COL_BORDER setSlotOrder:list]; }
- setRowOrder:(MiscIntList*)list
	{ return [self border:MISC_ROW_BORDER setSlotOrder:list]; }

- (char*) border:(MiscBorderType)b slotOrderAsString:(char*)buff
		size:(int)buff_size canExpand:(BOOL)canExpand
	{
	char* retval;
	MiscIntList* list = [[MiscIntList alloc] init];
	[self border:b slotOrder:list];
	retval = [list writeToString:buff size:buff_size canExpand:canExpand];
	[list free];
	return retval;
	}
- (char*) colOrderAsString:(char*)buff
		size:(int)buff_size canExpand:(BOOL)canExpand
	{ return [self border:MISC_COL_BORDER slotOrderAsString:buff
						size:buff_size canExpand:canExpand]; }
- (char*) rowOrderAsString:(char*)buff
		size:(int)buff_size canExpand:(BOOL)canExpand
	{ return [self border:MISC_ROW_BORDER slotOrderAsString:buff
						size:buff_size canExpand:canExpand]; }

- border:(MiscBorderType)b setSlotOrderFromString:(char const*)s
	{
	id retval;
	MiscIntList* list = [[MiscIntList alloc] init];
	[list readFromString:s];
	retval = [self border:b setSlotOrder:list];
	[list free];
	return retval;
	}
- setColOrderFromString:(char const*)s
	{ return [self border:MISC_COL_BORDER setSlotOrderFromString:s]; }
- setRowOrderFromString:(char const*)s
	{ return [self border:MISC_ROW_BORDER setSlotOrderFromString:s]; }


- border:(MiscBorderType)b slotSizes:(MiscIntList*)list
	{
	if (list != 0)
		{
		MiscTableBorder const* const bp = info[b]->border;
		int const lim = bp->count();
		[list empty];
		for (int i = 0;	 i < lim;  i++)
			[list addInt: (int) bp->getSize_P( i ) ];
		return self;
		}
	return 0;
	}
- colSizes:(MiscIntList*)list
	{ return [self border:MISC_COL_BORDER slotSizes:list]; }
- rowSizes:(MiscIntList*)list
	{ return [self border:MISC_ROW_BORDER slotSizes:list]; }

- border:(MiscBorderType)b setSlotSizes:(MiscIntList*)list
	{
	if (list != 0)
		{
		MiscTableBorder* const bp = info[b]->border;
		int const lim = bp->count();
		if ([list count] == lim)
			{
			for (int i = 0;	 i < lim;  i++)
				bp->setSize_P( i, (MiscPixels) [list intAt:i] );
			[self constrainSize];
			return self;
			}
		}
	return 0;
	}
- setColSizes:(MiscIntList*)list
	{ return [self border:MISC_COL_BORDER setSlotSizes:list]; }
- setRowSizes:(MiscIntList*)list
	{ return [self border:MISC_ROW_BORDER setSlotSizes:list]; }

- (char*) border:(MiscBorderType)b slotSizesAsString:(char*)buff
		size:(int)buff_size canExpand:(BOOL)canExpand
	{
	char* retval;
	MiscIntList* list = [[MiscIntList alloc] init];
	[self border:b slotSizes:list];
	retval = [list writeToString:buff size:buff_size canExpand:canExpand];
	[list free];
	return retval;
	}
- (char*) colSizesAsString:(char*)buff
		size:(int)buff_size canExpand:(BOOL)canExpand
	{ return [self border:MISC_COL_BORDER slotSizesAsString:buff
						size:buff_size canExpand:canExpand]; }
- (char*) rowSizesAsString:(char*)buff
		size:(int)buff_size canExpand:(BOOL)canExpand
	{ return [self border:MISC_ROW_BORDER slotSizesAsString:buff
						size:buff_size canExpand:canExpand]; }

- border:(MiscBorderType)b setSlotSizesFromString:(char const*)s
	{
	id retval;
	MiscIntList* list = [[MiscIntList alloc] init];
	[list readFromString:s];
	retval = [self border:b setSlotSizes:list];
	[list free];
	return retval;
	}
- setColSizesFromString:(char const*)s
	{ return [self border:MISC_COL_BORDER setSlotSizesFromString:s]; }
- setRowSizesFromString:(char const*)s
	{ return [self border:MISC_ROW_BORDER setSlotSizesFromString:s]; }



//-----------------------------------------------------------------------------
// Border Views
//-----------------------------------------------------------------------------
- (BOOL) setBorder:(MiscBorderType)type on:(BOOL)on
	{
	MiscBorderInfo& b = *(info[type]);
	if (b.isOn != on)
		{
		BOOL const other_border_is_on = info[otherBorder(type)]->isOn;
		b.isOn = on;
		if (on)
			{
			[self addSubview: b.clip];
			[window invalidateCursorRectsForView: b.view];
			if (other_border_is_on)
				[self addSubview: nullView];
			}
		else
			{
			[b.clip removeFromSuperview];
			if (other_border_is_on)
				[nullView removeFromSuperview];
			}
		[self tile];
		[self constrainSize];
		[self update];
		return YES;
		}
	return NO;
	}


//-----------------------------------------------------------------------------
// SLOT methods
//-----------------------------------------------------------------------------
- (MiscTableBorder*) border:(MiscBorderType)b
		{ return info[b]->border; }
- (BOOL) border:(MiscBorderType)b setTitlesOn: (BOOL) on_off
		{ return [self setBorder:b on:on_off]; }
- (BOOL) borderTitlesOn:(MiscBorderType)b
		{ return info[b]->isOn; }
- (MiscTableTitleMode) borderTitleMode:(MiscBorderType)b
		{ return info[b]->border->getTitleMode(); }
- (void) border:(MiscBorderType)b setTitleMode:(MiscTableTitleMode)x
		{
		MiscBorderInfo* const ip = info[b];
		if (ip->border->setTitleMode(x) && ip->isOn && ip->border->count() > 0)
			[ip->view update];
		}

- border:(MiscBorderType)b moveSlotFrom:(int)fromPos to:(int)toPos
		{ info[b]->border->moveFromTo(fromPos,toPos); return self; }
- (int) border:(MiscBorderType)b slotPosition:(int)n
		{ return info[b]->border->physicalToVisual(n); }
- (int) border:(MiscBorderType)b slotAtPosition:(int)n
		{ return info[b]->border->visualToPhysical(n); }

- (void) border:(MiscBorderType)b physicalToVisual:(MiscIntList*)list
		{
		for (unsigned int i = [list count];	 i-- > 0; )
			{
			MiscCoord_P const p = [list intAt:i];
			[list replaceIntAt:i with:[self border:b slotPosition:p]];
			}
		}

- (void) border:(MiscBorderType)b visualToPhysical:(MiscIntList*)list
		{
		for (unsigned int i = [list count];	 i-- > 0; )
			{
			MiscCoord_V const v = [list intAt:i];
			[list replaceIntAt:i with:[self border:b slotAtPosition:v]];
			}
		}


- (BOOL) sizeableSlots:(MiscBorderType)b
		{ return info[b]->border->isSizeable(); }
- (BOOL) draggableSlots:(MiscBorderType)b
		{ return info[b]->border->isDraggable(); }
- (BOOL) modifierDragSlots:(MiscBorderType)b
		{ return info[b]->border->isModifierDrag(); }
- (NXCoord) uniformSizeSlots:(MiscBorderType)b
		{ return (NXCoord) info[b]->border->getUniformSize(); }

- (NXCoord) border:(MiscBorderType)b slotAdjustedSize:(int)n
		{ return (NXCoord) info[b]->border->effectiveSize_P(n); }
- (NXCoord) border:(MiscBorderType)b slotSize:(int)n
		{ return (NXCoord) info[b]->border->getSize_P(n); }
- (NXCoord) border:(MiscBorderType)b slotMinSize:(int)n
		{ return (NXCoord) info[b]->border->getMinSize_P(n); }
- (NXCoord) border:(MiscBorderType)b slotMaxSize:(int)n
		{ return (NXCoord) info[b]->border->getMaxSize_P(n); }
- (NXCoord) border:(MiscBorderType)b slotDataSize:(int)n
		{ return (NXCoord) info[b]->border->getDataSize_P(n); }
- (BOOL) border:(MiscBorderType)b slotIsSizeable:(int)n
		{ return info[b]->border->isSizeable_P(n); }
- (BOOL) border:(MiscBorderType)b slotExpandsToData:(int)n
		{ return info[b]->border->isData_P(n); }
- (BOOL) border:(MiscBorderType)b slotIsAutosize:(int)n
		{ return info[b]->border->isSpringy_P(n); }
- (char const*) border:(MiscBorderType)b slotTitle:(int)n
		{ return info[b]->border->getTitle_P(n); }
- (int) border:(MiscBorderType)b slotTag:(int)n
		{ return info[b]->border->getTag_P(n); }
- (MiscTableCellStyle) border:(MiscBorderType)b slotCellType:(int)n
		{ return info[b]->border->getStyle_P(n); }
- border:(MiscBorderType)b slotCellPrototype:(int)n
		{ return info[b]->border->getPrototype_P(n); }

- (void) border:(MiscBorderType)b setSizeableSlots:(BOOL)flag
		{ info[b]->border->setSizeable(flag); }
- (void) border:(MiscBorderType)b setDraggableSlots:(BOOL)flag
		{ info[b]->border->setDraggable(flag); }
- (void) border:(MiscBorderType)b setModifierDragSlots:(BOOL)flag
		{ info[b]->border->setModifierDrag(flag); }
- (void) border:(MiscBorderType)b setUniformSizeSlots:(NXCoord)uniform_size
		{
		MiscBorderInfo* const ip = info[b];
		if (ip->border->setUniformSize((MiscPixels)floor(uniform_size)))
			{
			[self constrainSize];
			if (b == MISC_ROW_BORDER)
				{
				[self setLineScroll:uniform_size];
				[self setPageScroll:uniform_size];
				}
			[self update];
			}
		}

- (void) border:(MiscBorderType)b setSlot:(int)n size:(NXCoord)size
		{
		info[b]->border->setSize_P( n, (MiscPixels)floor(size) );
		[self constrainSize];
		}
- (void) border:(MiscBorderType)b setSlot:(int)n minSize:(NXCoord)size
		{
		info[b]->border->setMinSize_P( n, (MiscPixels)floor(size) );
		[self constrainSize];
		}
- (void) border:(MiscBorderType)b setSlot:(int)n maxSize:(NXCoord)size
		{
		info[b]->border->setMaxSize_P( n, (MiscPixels)floor(size) );
		[self constrainSize];
		}
- (void) border:(MiscBorderType)b setSlot:(int)n dataSize:(NXCoord)size
		{ info[b]->border->setDataSize_P( n, (MiscPixels)floor(size) ); }
- (void) border:(MiscBorderType)b setSlot:(int)n sizeable:(BOOL)flag
		{ info[b]->border->setSizeable_P( n, flag ); }
- (void) border:(MiscBorderType)b setSlot:(int)n expandsToData:(BOOL)flag
		{ info[b]->border->setData_P( n, flag ); }
- (void) border:(MiscBorderType)b setSlot:(int)n autosize:(BOOL)flag
		{
		info[b]->border->setSpringy_P( n, flag );
		[self constrainSize];
		}

- (void) border:(MiscBorderType)b setSlot:(int)n title:(char const*)title
		{
		MiscBorderInfo* const ip = info[b];
		if (ip->border->setTitle_P( n, title ) && ip->isOn)
			[self border:b drawSlotTitle:n];
		}

- (void) border:(MiscBorderType)b setSlot:(int)n tag:(int)x
		{ info[b]->border->setTag_P( n, x ); }
- (void) border:(MiscBorderType)b setSlot:(int)n
				cellType:(MiscTableCellStyle)type
		{ info[b]->border->setStyle_P(n,type); }
- (void) border:(MiscBorderType)b setSlot:(int)n cellPrototype:p
		{ info[b]->border->setPrototype_P(n,p); }



//-----------------------------------------------------------------------------
// COL methods
//-----------------------------------------------------------------------------
- (MiscTableBorder*) colBorder
		{ return colInfo.border; }
- (BOOL) colTitlesOn
		{ return [self borderTitlesOn:MISC_COL_BORDER]; }
- (BOOL) setColTitlesOn:(BOOL)x
		{ return [self setBorder:MISC_COL_BORDER on:x]; }
- (MiscTableTitleMode) colTitleMode
		{ return [self borderTitleMode:MISC_COL_BORDER]; }
- (void) setColTitleMode:(MiscTableTitleMode)x
		{ [self border:MISC_COL_BORDER setTitleMode:x]; }

- moveColFrom:(int)fromPos to:(int)toPos
		{ return [self border:MISC_COL_BORDER moveSlotFrom:fromPos to:toPos]; }
- (int) colPosition:(int)n
		{ return [self border:MISC_COL_BORDER slotPosition:n]; }
- (int) colAtPosition:(int)n
		{ return [self border:MISC_COL_BORDER slotAtPosition:n]; }

- (BOOL) sizeableCols
		{ return [self sizeableSlots:MISC_COL_BORDER]; }
- (BOOL) draggableCols
		{ return [self draggableSlots:MISC_COL_BORDER]; }
- (BOOL) modifierDragCols
		{ return [self modifierDragSlots:MISC_COL_BORDER]; }
- (NXCoord) uniformSizeCols
		{ return [self uniformSizeSlots:MISC_COL_BORDER]; }

- (NXCoord) colAdjustedSize:(int)n
		{ return [self border:MISC_COL_BORDER slotAdjustedSize:n]; }
- (NXCoord) colSize:(int)n
		{ return [self border:MISC_COL_BORDER slotSize:n]; }
- (NXCoord) colMinSize:(int)n
		{ return [self border:MISC_COL_BORDER slotMinSize:n]; }
- (NXCoord) colMaxSize:(int)n
		{ return [self border:MISC_COL_BORDER slotMaxSize:n]; }
- (NXCoord) colDataSize:(int)n
		{ return [self border:MISC_COL_BORDER slotDataSize:n]; }
- (BOOL) colIsSizeable:(int)n
		{ return [self border:MISC_COL_BORDER slotIsSizeable:n]; }
- (BOOL) colExpandsToData:(int)n
		{ return [self border:MISC_COL_BORDER slotExpandsToData:n]; }
- (BOOL) colIsAutosize:(int)n
		{ return [self border:MISC_COL_BORDER slotIsAutosize:n]; }
- (char const*) colTitle:(int)n
		{ return [self border:MISC_COL_BORDER slotTitle:n]; }
- (int) colTag:(int)n
		{ return [self border:MISC_COL_BORDER slotTag:n]; }
- (MiscTableCellStyle) colCellType:(int)n
		{ return [self border:MISC_COL_BORDER slotCellType:n]; }
- colCellPrototype:(int)n
		{ return [self border:MISC_COL_BORDER slotCellPrototype:n]; }

- (void) setSizeableCols:(BOOL)flag
		{ [self border:MISC_COL_BORDER setSizeableSlots:flag]; }
- (void) setDraggableCols:(BOOL)flag
		{ [self border:MISC_COL_BORDER setDraggableSlots:flag]; }
- (void) setModifierDragCols:(BOOL)flag
		{ [self border:MISC_COL_BORDER setModifierDragSlots:flag]; }
- (void) setUniformSizeCols:(NXCoord)size
		{ [self border:MISC_COL_BORDER setUniformSizeSlots:size]; }

- (void) setCol:(int)n size:(NXCoord)size
		{ [self border:MISC_COL_BORDER setSlot:n size:size]; }
- (void) setCol:(int)n minSize:(NXCoord)size
		{ [self border:MISC_COL_BORDER setSlot:n minSize:size]; }
- (void) setCol:(int)n maxSize:(NXCoord)size
		{ [self border:MISC_COL_BORDER setSlot:n maxSize:size]; }
- (void) setCol:(int)n dataSize:(NXCoord)size
		{ [self border:MISC_COL_BORDER setSlot:n dataSize:size]; }
- (void) setCol:(int)n sizeable:(BOOL)flag
		{ [self border:MISC_COL_BORDER setSlot:n sizeable:flag]; }
- (void) setCol:(int)n expandsToData:(BOOL)flag
		{ [self border:MISC_COL_BORDER setSlot:n expandsToData:flag]; }
- (void) setCol:(int)n autosize:(BOOL)flag
		{ [self border:MISC_COL_BORDER setSlot:n autosize:flag]; }
- (void) setCol:(int)n title:(char const*)title
		{ [self border:MISC_COL_BORDER setSlot:n title:title]; }
- (void) setCol:(int)n tag:(int)x
		{ [self border:MISC_COL_BORDER setSlot:n tag:x]; }
- (void) setCol:(int)n cellType:(MiscTableCellStyle)x
		{ [self border:MISC_COL_BORDER setSlot:n cellType:x]; }
- (void) setCol:(int)n cellPrototype:p
		{ [self border:MISC_COL_BORDER setSlot:n cellPrototype:p]; }


//-----------------------------------------------------------------------------
// ROW methods
//-----------------------------------------------------------------------------
- (MiscTableBorder*) rowBorder
		{ return rowInfo.border; }
- (BOOL) rowTitlesOn
		{ return [self borderTitlesOn:MISC_ROW_BORDER]; }
- (BOOL) setRowTitlesOn:(BOOL)x
		{ return [self setBorder:MISC_ROW_BORDER on:x]; }
- (MiscTableTitleMode) rowTitleMode
		{ return [self borderTitleMode:MISC_ROW_BORDER]; }
- (void) setRowTitleMode:(MiscTableTitleMode)x
		{ [self border:MISC_ROW_BORDER setTitleMode:x]; }

- moveRowFrom:(int)fromPos to:(int)toPos
		{ return [self border:MISC_ROW_BORDER moveSlotFrom:fromPos to:toPos]; }
- (int) rowPosition:(int)n
		{ return [self border:MISC_ROW_BORDER slotPosition:n]; }
- (int) rowAtPosition:(int)n
		{ return [self border:MISC_ROW_BORDER slotAtPosition:n]; }

- (BOOL) sizeableRows
		{ return [self sizeableSlots:MISC_ROW_BORDER]; }
- (BOOL) draggableRows
		{ return [self draggableSlots:MISC_ROW_BORDER]; }
- (BOOL) modifierDragRows
		{ return [self modifierDragSlots:MISC_ROW_BORDER]; }
- (NXCoord) uniformSizeRows
		{ return [self uniformSizeSlots:MISC_ROW_BORDER]; }

- (NXCoord) rowAdjustedSize:(int)n
		{ return [self border:MISC_ROW_BORDER slotAdjustedSize:n]; }
- (NXCoord) rowSize:(int)n
		{ return [self border:MISC_ROW_BORDER slotSize:n]; }
- (NXCoord) rowMinSize:(int)n
		{ return [self border:MISC_ROW_BORDER slotMinSize:n]; }
- (NXCoord) rowMaxSize:(int)n
		{ return [self border:MISC_ROW_BORDER slotMaxSize:n]; }
- (NXCoord) rowDataSize:(int)n
		{ return [self border:MISC_ROW_BORDER slotDataSize:n]; }
- (BOOL) rowIsSizeable:(int)n
		{ return [self border:MISC_ROW_BORDER slotIsSizeable:n]; }
- (BOOL) rowExpandsToData:(int)n
		{ return [self border:MISC_ROW_BORDER slotExpandsToData:n]; }
- (BOOL) rowIsAutosize:(int)n
		{ return [self border:MISC_ROW_BORDER slotIsAutosize:n]; }
- (char const*) rowTitle:(int)n
		{ return [self border:MISC_ROW_BORDER slotTitle:n]; }
- (int) rowTag:(int)n
		{ return [self border:MISC_ROW_BORDER slotTag:n]; }
- (MiscTableCellStyle) rowCellType:(int)n
		{ return [self border:MISC_ROW_BORDER slotCellType:n]; }
- rowCellPrototype:(int)n
		{ return [self border:MISC_ROW_BORDER slotCellPrototype:n]; }

- (void) setSizeableRows:(BOOL)flag
		{ [self border:MISC_ROW_BORDER setSizeableSlots:flag]; }
- (void) setDraggableRows:(BOOL)flag
		{ [self border:MISC_ROW_BORDER setDraggableSlots:flag]; }
- (void) setModifierDragRows:(BOOL)flag
		{ [self border:MISC_ROW_BORDER setModifierDragSlots:flag]; }
- (void) setUniformSizeRows:(NXCoord)size
		{ [self border:MISC_ROW_BORDER setUniformSizeSlots:size]; }

- (void) setRow:(int)n size:(NXCoord)size
		{ [self border:MISC_ROW_BORDER setSlot:n size:size]; }
- (void) setRow:(int)n minSize:(NXCoord)size
		{ [self border:MISC_ROW_BORDER setSlot:n minSize:size]; }
- (void) setRow:(int)n maxSize:(NXCoord)size
		{ [self border:MISC_ROW_BORDER setSlot:n maxSize:size]; }
- (void) setRow:(int)n dataSize:(NXCoord)size
		{ [self border:MISC_ROW_BORDER setSlot:n dataSize:size]; }
- (void) setRow:(int)n sizeable:(BOOL)flag
		{ [self border:MISC_ROW_BORDER setSlot:n sizeable:flag]; }
- (void) setRow:(int)n expandsToData:(BOOL)flag
		{ [self border:MISC_ROW_BORDER setSlot:n expandsToData:flag]; }
- (void) setRow:(int)n autosize:(BOOL)flag
		{ [self border:MISC_ROW_BORDER setSlot:n autosize:flag]; }
- (void) setRow:(int)n title:(char const*)title
		{ [self border:MISC_ROW_BORDER setSlot:n title:title]; }
- (void) setRow:(int)n tag:(int)x
		{ [self border:MISC_ROW_BORDER setSlot:n tag:x]; }
- (void) setRow:(int)n cellType:(MiscTableCellStyle)x
		{ [self border:MISC_ROW_BORDER setSlot:n cellType:x]; }
- (void) setRow:(int)n cellPrototype:p
		{ [self border:MISC_ROW_BORDER setSlot:n cellPrototype:p]; }

//-----------------------------------------------------------------------------
// - read:selector:
//-----------------------------------------------------------------------------
- (SEL) read:(int)ver selector:(NXTypedStream*)stream
	{
	SEL cmd = 0;
	char* str;
	NXReadType( stream, @encode(char*), &str );
	if (str != 0)
		{
		if (*str != '\0')
			cmd = sel_getUid( str );
		free( str );
		}
	return cmd;
	}


//-----------------------------------------------------------------------------
// - read:globalInfo:
//
//		NOTE: Cannot archive sort_entry_func and sort_slot_func because they 
//				are function addresses.	 
//-----------------------------------------------------------------------------
- (void) read:(int)ver globalInfo:(NXTypedStream*)stream
	{
	NXReadType( stream, @encode(int), &tag );
	NXReadType( stream, @encode(BOOL), &enabled );
	NXReadType( stream, @encode(BOOL), &lazy );
	NXReadType( stream, @encode(MiscSelectionMode), &mode );
	font = NXReadObject( stream );
	textColor = NXReadColor( stream );
	backgroundColor = NXReadColor( stream );
	highlightTextColor = NXReadColor( stream );
	highlightBackgroundColor = NXReadColor( stream );
	nextText	= NXReadObject( stream );
	previousText= NXReadObject( stream );
	delegate	= NXReadObject( stream );
	dataDelegate= NXReadObject( stream );
	target		= NXReadObject( stream );
	doubleTarget= NXReadObject( stream );
	action = [self read:ver selector:stream];
	doubleAction = [self read:ver selector:stream];
	}


//-----------------------------------------------------------------------------
// - read:border:stream:
//-----------------------------------------------------------------------------
- (void) read:(int)ver border:(MiscBorderInfo*)p stream:(NXTypedStream*)stream
	{
	NXZone* const z = [self zone];
	int n;
	if (p->sort_vector != 0)
		NXZoneFree( z, p->sort_vector );
	NXReadType( stream, @encode(int), &(n) );
	if (n > 0)
		{
		p->sort_vector_len = n;
		p->sort_vector = (int*) NXZoneMalloc( z, n * sizeof(int) );
		for (int i = 0;	 i < n;	 i++)
			NXReadType( stream, @encode(int), &(p->sort_vector[i]) );
		}
	else
		{
		p->sort_vector_len = 0;
		p->sort_vector = 0;
		}
	NXReadType( stream, @encode(BOOL), &(p->isOn) );
	NXReadType( stream, @encode(BOOL), &(p->autoSort) );
	NXReadType( stream, @encode(BOOL), &(p->constrain_min) );
	NXReadType( stream, @encode(BOOL), &(p->constrain_max) );
	if (p->border == 0)
		p->border = new( NXZoneMalloc( z, sizeof(*(p->border)) ) )
						MiscTableBorder( MISC_ROW_BORDER );
	p->border->read( stream );	// FIXME: Should send ver.
	}


//-----------------------------------------------------------------------------
// - read:cells:
//
// FIXME: What happens when a non-empty non-lazy TableScroll reads the data
// from a lazy, empty one.	Does the memory get freed?
//-----------------------------------------------------------------------------
- (void) read:(int)ver cells: (NXTypedStream*) stream
	{
	NXReadType( stream, @encode(int), &num_cols );
	NXReadType( stream, @encode(int), &num_rows );
	max_rows = num_rows;
	if (lazy)
		{
		max_cells = 0;
		cells = 0;
		}
	else // (!lazy)
		{
		max_cells = max_rows * num_cols;
		if (max_cells == 0)
			cells = 0;
		else
			{
			int const nbytes = max_cells * sizeof(*cells);
			cells = (id*) NXZoneMalloc( [self zone], nbytes );
			id* p = cells;
			for (int r = 0;		 r < num_rows;	r++)
				for (int c = 0;	 c < num_cols;	c++)
					*p++ = NXReadObject( stream );
			}
		}
	}


//-----------------------------------------------------------------------------
// - read:
//-----------------------------------------------------------------------------
- read: (NXTypedStream*) stream
	{
	[super read:stream];
	char const* class_name = [[MiscTableScroll class] name];
	int ver = NXTypedStreamClassVersion( stream, class_name );
	if (((unsigned int)ver) > ((unsigned int)MISC_TS_VERSION))
		[self error:"%s: old library (version %d), can't -read: "
						"new object (version %d).\n",
						class_name,
						MISC_TS_VERSION, ver ];
	[self read:ver globalInfo:stream];
	[self read:ver border:&colInfo stream:stream];
	[self read:ver border:&rowInfo stream:stream];
	[self read:ver cells:stream];
	[self doInit:ver];
	return self;
	}


//-----------------------------------------------------------------------------
// - writeSelector:stream:
//-----------------------------------------------------------------------------
- (void) writeSelector: (SEL) cmd stream: (NXTypedStream*) stream
	{
	char const* str = 0;
	if (cmd != 0)
		str = sel_getName( cmd );
	if (str == 0)						// not an 'else if'
		str = "";
	NXWriteType( stream, @encode(char*), &str );
	}


//-----------------------------------------------------------------------------
// - writeGlobalInfo:
//
//		NOTE: Cannot archive sort_entry_func and sort_slot_func because they 
//				are function addresses.	 
//-----------------------------------------------------------------------------
- (void) writeGlobalInfo: (NXTypedStream*) stream
	{
	NXWriteType( stream, @encode(int), &tag );
	NXWriteType( stream, @encode(BOOL), &enabled );
	NXWriteType( stream, @encode(BOOL), &lazy );
	NXWriteType( stream, @encode(MiscSelectionMode), &mode );
	NXWriteObject( stream, font );
	NXWriteColor( stream, textColor );
	NXWriteColor( stream, backgroundColor );
	NXWriteColor( stream, highlightTextColor );
	NXWriteColor( stream, highlightBackgroundColor );
	NXWriteObjectReference( stream, nextText );
	NXWriteObjectReference( stream, previousText );
	NXWriteObjectReference( stream, delegate );
	NXWriteObjectReference( stream, dataDelegate );
	NXWriteObjectReference( stream, target );
	NXWriteObjectReference( stream, doubleTarget );
	[self writeSelector: action stream: stream];
	[self writeSelector: doubleAction stream: stream];
	}


//-----------------------------------------------------------------------------
// - writeBorder:stream:
//-----------------------------------------------------------------------------
- (void) writeBorder:(MiscBorderInfo*)p stream:(NXTypedStream*)stream
	{
	int const n = p->sort_vector_len;
	NXWriteType( stream, @encode(int), &(n) );
	if (n > 0)
		for (int i = 0;	 i < n;	 i++)
			NXWriteType( stream, @encode(int), &(p->sort_vector[i]) );
	NXWriteType( stream, @encode(BOOL), &(p->isOn) );
	NXWriteType( stream, @encode(BOOL), &(p->autoSort) );
	NXWriteType( stream, @encode(BOOL), &(p->constrain_min) );
	NXWriteType( stream, @encode(BOOL), &(p->constrain_max) );
	p->border->write( stream );
	}


//-----------------------------------------------------------------------------
// - writeCells:
//-----------------------------------------------------------------------------
- (void) writeCells: (NXTypedStream*) stream
	{
	NXWriteType( stream, @encode(int), &num_cols );
	NXWriteType( stream, @encode(int), &num_rows );
	if (!lazy)
		for (int r = 0;	 r < num_rows;	r++)
			for (int c = 0;	 c < num_cols;	c++)
				NXWriteObject( stream, [self cellAt:r:c] );
	}


//-----------------------------------------------------------------------------
// - write:
//-----------------------------------------------------------------------------
- write: (NXTypedStream*) stream
	{
	// Ensure that subviews are NOT archived.
	if (colInfo.isOn)	[colInfo.clip removeFromSuperview];
	if (rowInfo.isOn)	[rowInfo.clip removeFromSuperview];
	if (rowInfo.isOn && colInfo.isOn) [nullView removeFromSuperview];
	[tableView removeFromSuperview];

	[super write:stream];
	[self writeGlobalInfo:stream];
	[self writeBorder:&colInfo stream:stream];
	[self writeBorder:&rowInfo stream:stream];
	[self writeCells:stream];

	// Restore state.
	[self setDocView: tableView];
	if (colInfo.isOn)	[self addSubview:colInfo.clip];
	if (rowInfo.isOn)	[self addSubview:rowInfo.clip];
	if (rowInfo.isOn && colInfo.isOn) [self addSubview:nullView];
	return self;
	}


//-----------------------------------------------------------------------------
// DRAWING
//-----------------------------------------------------------------------------

- drawCellAt:(int)row :(int)col
	{
	[tableView drawCellAt:row:col];
	[window flushWindow];
	return self;
	}
- drawRow:(int)row
	{
	[tableView drawRow:row];
	[window flushWindow];
	return self;
	}
- drawCol:(int)col
	{
	[tableView drawCol:col];
	[window flushWindow];
	return self;
	}
- border:(MiscBorderType)b drawSlot:(int)n
	{ return (b == MISC_COL_BORDER) ? [self drawCol:n] : [self drawRow:n]; }

- border:(MiscBorderType)b drawSlotTitle:(int)n			// physical position.
	{
	MiscBorderInfo* const ip = info[b];
	if (ip->isOn)										// visual position.
		{
		[ip->view drawSlot: [self border:b slotPosition:n]];
		[window flushWindow];
		}
	return self;
	}
- drawRowTitle:(int)n { return [self border:MISC_ROW_BORDER drawSlotTitle:n]; }
- drawColTitle:(int)n { return [self border:MISC_COL_BORDER drawSlotTitle:n]; }

- (void) reflectSelection
	{
	if ([self canDraw])
		{
		[window disableFlushWindow];
		[rowInfo.view reflectSelection];
		[colInfo.view reflectSelection];
		[tableView reflectSelection];
		[[window reenableFlushWindow] flushWindow];
		}
	else
		[self setNeedsDisplay:YES];
	}

- setAutodisplay:(BOOL) x
	{
	[rowInfo.view setAutodisplay:x];
	[colInfo.view setAutodisplay:x];
	[tableView setAutodisplay:x];
	return [super setAutodisplay:x];
	}

- (void) scrollCellToVisible: (int)row : (int) col
		{ [tableView scrollCellToVisible: row:col]; }
- (void) scrollRowToVisible: (int)row { [tableView scrollRowToVisible:row]; }
- (void) scrollColToVisible: (int)col { [tableView scrollColToVisible:col]; }
- scrollSelToVisible
	{
	if ([self hasRowSelection])
		[self scrollRowToVisible: [self selectedRow]];
	else if ([self hasColSelection])
		[self scrollColToVisible: [self selectedCol]];
	return self;
	}


//-----------------------------------------------------------------------------
// MOUSE & KEYBOARD TRACKING
//-----------------------------------------------------------------------------
- (void) trackBy: (MiscBorderType)b { [tableView trackBy:b]; }
- (MiscBorderType) trackingBy { return [tableView trackingBy]; }

@end
